import * as nodeuuid from 'node-uuid';

import { Redis } from "./redis";
import { RedisClient } from 'redis';
import { Logger, LogFactory } from './log';

export type RPCCallback = (method:string, param:object) => object;

export class RPC{
    log:Logger = LogFactory.inst().getLogger("RPC");
    client:Redis;
    server:Redis;

    constructor(host:string, port:number){
        this.client = new Redis(host, port);
        this.server = new Redis(host, port);
    }

    async call(rpc:string, method:string, param:object, timeout:number=1):Promise<any|false>{
        return new Promise<any|false>((resolve, reject)=>{
            //this.log.info("rpc call:", rpc, method, param);
            let data = { id: nodeuuid.v4(), method:method, param: param };
            this.client.rc.lpush(rpc, JSON.stringify(data), async (err:Error|null, number)=>{
                if (err){
                    this.log.error("error:", err);
                    reject(err);
                }else{
                    this.client.rc.brpop(rpc+':'+data.id, timeout, (err:Error|null, reply:string[])=>{
                        if (err) {
                            this.log.error("error:", err);
                            reject(err);
                        } else {
                            if (reply && reply.length == 2) {
                                resolve(reply[1]);
                            } else {
                                this.log.error("error:", "timeout");
                                reject("timeout");
                            }
                        }
                    });
                }
            });
        });
    }

    listen(rpc: string, callback: RPCCallback) {
        let run=(rc:RedisClient, rpc:string, callback:RPCCallback)=>{
            rc.brpop(rpc, 0, async (err: Error | null, reply: string[]) => {
                if (err) {
                    this.log.error("error:", err);
                } else if (reply) {
                    let data = JSON.parse(reply[1]);
                    let ret = await callback(data.method, data.param);
                    let retChannel = rpc + ":" + data.id;
                    rc.lpush(retChannel, JSON.stringify(ret), (err: Error | null, reply: number) => {
                        if (err) {
                            this.log.error("error:", err);
                        } else {
                            rc.expire(retChannel, 3, (err: Error | null, reply: number) => {
                                if (err) {
                                    this.log.error("error:", err);
                                }
                            });
                            run(rc, rpc, callback);
                        }
                    });
                }
            });
        };
        run(this.server.rc.duplicate(), rpc, callback);
    }
}